﻿/*                       NOTICE
 * 
 * This software was produced for the U. S. Government
 * under Contract No. FA8721-10-C-0001, and is
 * subject to the Rights in Noncommercial Computer Software
 * and Noncommercial Computer Software Documentation Clause
 * (DFARS) 252.227-7014 (JUN 1995)
 * 
 * Approved for Public Release: 11-2435
 * © 2011 The MITRE Corporation. All Rights Reserved. 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
using RabbitMQ.Client;
using RabbitMQ.Client.Content;
using RabbitMQ.Client.MessagePatterns;
using RabbitMQ.Util;
using GRIP.Gestures;
using TUIO;

namespace GRIP
{
    public class GestureFrameReadyEventArgs : EventArgs
    {
        private XmlDocument doc;

        public GestureFrameReadyEventArgs(XmlDocument FrameXml)
        {
            doc = FrameXml;
        }

        public XmlDocument FrameXml
        {
            get { return doc; }
        }
    }

    public enum RecognizerMode
    {
        Standalone,
        Server,
        Client
    }

    public static class Recognizer
    {
        public static RecognizerMode Mode = RecognizerMode.Standalone;
        public static List<GestureEvent<Gesture>> PendingEvents = new List<GestureEvent<Gesture>>();
        public static Dictionary<string, List<GestureConstructor>> Constructors = new Dictionary<string, List<GestureConstructor>>();
        private static Dictionary<string, KinectAdapter> LocalAdapters = new Dictionary<string, KinectAdapter>();
        private static Dictionary<string, KinectNetworkAdapter> RemoteAdapters = new Dictionary<string, KinectNetworkAdapter>();
        private static long GestureIDCount;

        private static object ProcessLock = new object();

        static XmlSerializer serializer = new XmlSerializer(typeof(ContactFrameReadyEventArgs));
        static IConnection conn;
        static IModel channel;
        static SimpleRpcServer contactReceiver;
        static SimpleRpcClient contactSender;
        static IBasicProperties basicProperties;
        private static int FrameCount;
        public static event EventHandler<GestureFrameReadyEventArgs> GestureFrameReady;

        static EventHandler<ContactFrameReadyEventArgs> StandaloneHandler = new EventHandler<ContactFrameReadyEventArgs>(StandaloneContactFrameReady);
        static EventHandler<ContactFrameReadyEventArgs> ServerHandler = new EventHandler<ContactFrameReadyEventArgs>(ServerContactFrameReady);
        static EventHandler<ContactFrameReadyEventArgs> ClientHandler = new EventHandler<ContactFrameReadyEventArgs>(ClientContactFrameReady);

        public static event EventHandler<NewAdapterArgs> NewAdapter;

        public static void SetAdapter(KinectAdapter adapter)
        {
            LocalAdapters[adapter.Source] = adapter;
            if (Mode == RecognizerMode.Standalone)
                adapter.ContactFrameReady += StandaloneHandler;
            else if (Mode == RecognizerMode.Server)
                adapter.ContactFrameReady += ServerHandler;
            else
                adapter.ContactFrameReady += ClientHandler;

            List<GestureConstructor> localConstructorList = new List<GestureConstructor>();
            Constructors[adapter.Source] = localConstructorList;

            if (NewAdapter != null)
                NewAdapter(adapter.Source, new NewAdapterArgs(adapter));
            //localConstructorList.Add(new KinectHandConstructor(KinectHandConstructor.HandCardinality.Left, adapter));
            //localConstructorList.Add(new KinectHandConstructor(KinectHandConstructor.HandCardinality.Right, adapter));
            //Constructors.Add(new ZoomConstructor());
        }

        public static void SetRemoteAdapter(KinectNetworkAdapter adapter, params GestureConstructor[] constructors)
        {
            RemoteAdapters[adapter.Source] = adapter;
            List<GestureConstructor> localConstructorList = new List<GestureConstructor>();
            Constructors[adapter.Source] = localConstructorList;

            if (NewAdapter != null)
                NewAdapter(adapter.Source, new NewAdapterArgs(adapter));
            //localConstructorList.Add(new KinectHandConstructor(KinectHandConstructor.HandCardinality.Left, adapter));
            //localConstructorList.Add(new KinectHandConstructor(KinectHandConstructor.HandCardinality.Right, adapter));
            //Constructors.Add(new ZoomConstructor());
        }

        private static void StandaloneContactFrameReady(object sender, ContactFrameReadyEventArgs frame)
        {
            Task.Factory.StartNew(() =>
            {
                FrameCount++;
                ProcessFrame(frame);
                SendGestureFrameXML(frame);
            });
        }

        private static void ProcessFrame(ContactFrameReadyEventArgs frame)
        {
            lock (ProcessLock)
            {
                foreach (GestureConstructor constructor in Constructors[frame.AdapterSource])
                    constructor.ConstructGestures(frame);
                foreach (GestureEvent<Gesture> g_event in PendingEvents)
                {
                    if (g_event.EventType == GestureEventType.Added)
                        g_event.Gesture.OnAdded(frame);
                    else if (g_event.EventType == GestureEventType.Changed)
                        g_event.Gesture.OnChanged(frame);
                    else if (g_event.EventType == GestureEventType.Removed)
                        g_event.Gesture.OnRemoved(frame);
                }

                PendingEvents.Clear();
            }
        }

        private static void ClientContactFrameReady(object sender, ContactFrameReadyEventArgs frame)
        {
            Task.Factory.StartNew(() =>
            {
                System.IO.MemoryStream stream = new System.IO.MemoryStream();
                serializer.Serialize(stream, frame);
                contactSender.Cast(basicProperties, stream.ToArray());
                stream.Close();
            });
        }

        private static void ServerContactFrameReady(object sender, ContactFrameReadyEventArgs frame)
        {
            Task.Factory.StartNew(() =>
            {
                System.IO.MemoryStream stream = new System.IO.MemoryStream();
                serializer.Serialize(stream, frame);
                contactSender.Cast(basicProperties, stream.ToArray());
                stream.Close();

                frame.FrameNumber = FrameCount++;
                ProcessFrame(frame);
            });
        }

        private static void SendGestureFrameXML(ContactFrameReadyEventArgs frame)
        {
            if (GestureFrameReady != null && PendingEvents.Count > 0)
            {
                XmlDocument doc = new XmlDocument();
                XmlElement root = doc.CreateElement("GestureFrame");
                root.SetAttribute("Number", FrameCount.ToString());
                root.SetAttribute("Time", frame.Time.ToString());
                root.SetAttribute("Source", frame.AdapterSource);

                XmlElement addedElement = doc.CreateElement("Added");
                XmlElement changedElement = doc.CreateElement("Changed");
                XmlElement removedElement = doc.CreateElement("Removed");

                foreach (GestureEvent<Gesture> g_event in PendingEvents)
                {
                    if (g_event.EventType == GestureEventType.Added)
                        addedElement.AppendChild(doc.ImportNode(g_event.Gesture.ToXML(), true));
                    else if (g_event.EventType == GestureEventType.Changed)
                        changedElement.AppendChild(doc.ImportNode(g_event.Gesture.ToXML(), true));
                    else if (g_event.EventType == GestureEventType.Removed)
                        removedElement.AppendChild(doc.ImportNode(g_event.Gesture.ToXML(), true));
                }

                root.AppendChild(addedElement);
                root.AppendChild(changedElement);
                root.AppendChild(removedElement);
                doc.AppendChild(root);

                GestureFrameReady(frame.AdapterSource, new GestureFrameReadyEventArgs(doc));
            }
        }

        public static long GetNextId()
        {
            return ++GestureIDCount;
        }

        public static void Connect(string HostAddress)
        {
            ConnectionFactory factory = new ConnectionFactory();
            factory.Endpoint = new AmqpTcpEndpoint(HostAddress);
            conn = factory.CreateConnection();
            channel = conn.CreateModel();

            basicProperties = channel.CreateBasicProperties();
            basicProperties.Expiration = "100";

            channel.QueueDeclare("ContactServer", true, false, true, null);
            channel.QueueDeclare("ContactClient", true, false, true, null);
            if (Mode == RecognizerMode.Server)
            {
                contactReceiver = new ServerContactReceiver(new Subscription(channel, "ContactServer"));
                contactSender = new SimpleRpcClient(channel, "ContactClient");
                foreach (KinectAdapter adapter in LocalAdapters.Values)
                {
                    adapter.ContactFrameReady -= StandaloneHandler;
                    adapter.ContactFrameReady += ServerHandler;
                }
            }
            else if (Mode == RecognizerMode.Client)
            {
                contactReceiver = new ClientContactReceiver(new Subscription(channel, "ContactClient"));
                contactSender = new SimpleRpcClient(channel, "ContactServer");
                foreach (KinectAdapter adapter in LocalAdapters.Values)
                {
                    adapter.ContactFrameReady -= StandaloneHandler;
                    adapter.ContactFrameReady += ClientHandler;
                }
            }
        }

        public static IModel CreateChannel()
        {
            return conn.CreateModel();
        }

        public static void Disconnect()
        {
            if (Mode == RecognizerMode.Standalone)
                return;

            if (Mode == RecognizerMode.Client)
            {
                foreach (KinectAdapter adapter in LocalAdapters.Values)
                {
                    adapter.ContactFrameReady += StandaloneHandler;
                    adapter.ContactFrameReady -= ClientHandler;
                }
            }
            else if (Mode == RecognizerMode.Server)
            {
                foreach (KinectAdapter adapter in LocalAdapters.Values)
                {
                    adapter.ContactFrameReady += StandaloneHandler;
                    adapter.ContactFrameReady -= ServerHandler;
                }
            }

            Mode = RecognizerMode.Standalone;
            contactSender.Close();
            contactReceiver.Close();
            channel.Close();
            conn.Close();
            channel.Dispose();
            conn.Dispose();
        }

        private class ServerContactReceiver : SimpleRpcServer
        {
            private System.Threading.Thread messageThread;
            public ServerContactReceiver(Subscription sub)
                : base(sub)
            {
                messageThread = new System.Threading.Thread(MainLoop);
                messageThread.Start();
            }

            public override void HandleCast(bool isRedelivered, IBasicProperties requestProperties, byte[] body)
            {
                System.IO.MemoryStream stream = new System.IO.MemoryStream(body);
                ContactFrameReadyEventArgs frame = (ContactFrameReadyEventArgs)serializer.Deserialize(stream);
                stream.Close();

                string source = frame.AdapterSource;

                if (!RemoteAdapters.ContainsKey(source))
                    SetRemoteAdapter(new KinectNetworkAdapter(source));
                RemoteAdapters[source].HandleNewFrame(frame);

                ServerContactFrameReady(source, frame);
            }
        }

        private class ClientContactReceiver : SimpleRpcServer
        {
            private System.Threading.Thread messageThread;
            public ClientContactReceiver(Subscription sub)
                : base(sub)
            {
                messageThread = new System.Threading.Thread(MainLoop);
                messageThread.Start();
            }

            public override void HandleCast(bool isRedelivered, IBasicProperties requestProperties, byte[] body)
            {
                System.IO.MemoryStream stream = new System.IO.MemoryStream(body);
                ContactFrameReadyEventArgs frame = (ContactFrameReadyEventArgs)serializer.Deserialize(stream);
                stream.Close();

                if (!RemoteAdapters.ContainsKey(frame.AdapterSource))
                    SetRemoteAdapter(new KinectNetworkAdapter(frame.AdapterSource));
                RemoteAdapters[frame.AdapterSource].HandleNewFrame(frame);

                ProcessFrame(frame);
            }
        }
    }
}